package com.yulang.security.config;

import com.yulang.security.filter.ImageCodeValidateFilter;
import com.yulang.security.handler.CustomLogoutHandler;
import com.yulang.security.mobile.MobileValidateFilter;
import com.yulang.security.handler.CustomAuthFailureHandler;
import com.yulang.security.handler.CustomAuthSuccessHandler;
import com.yulang.security.mobile.MobileAuthConfig;
import com.yulang.security.properties.AuthProperties;
import com.yulang.security.properties.CustomSpringSecurityProperties;
import com.yulang.security.provider.AuthConfigureManager;
import com.yulang.security.strategy.CustomSessionExpiredStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.session.InvalidSessionStrategy;

import javax.sql.DataSource;


/**
 * spring security 核心配置类 需要继承 WebSecurityConfigurerAdapter
 */
@EnableWebSecurity // 开启 security 过滤器链
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *  密码加密器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private CustomSpringSecurityProperties customSpringSecurityProperties;

    @Qualifier("userDetailsServiceImpl")
    @Autowired
    private UserDetailsService userDetailsServiceImpl;

    @Autowired
    private CustomAuthSuccessHandler customAuthSuccessHandler;


    @Autowired
    private CustomAuthFailureHandler customAuthFailureHandler;

    @Autowired
    private ImageCodeValidateFilter imageCodeValidateFilter;


    @Autowired
    private MobileValidateFilter mobileValidateFilter;

    @Autowired
    private MobileAuthConfig mobileAuthConfig;

    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;

    @Autowired
    private CustomSessionExpiredStrategy customSessionExpiredStrategy;

    @Autowired
    private AuthConfigureManager authConfigureManager;


    @Autowired
    private DataSource dataSource;

    public CustomAuthSuccessHandler getCustomAuthSuccessHandler() {
        return customAuthSuccessHandler;
    }

    @Bean
    public JdbcTokenRepositoryImpl jdbcTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }


    /**
     * 认证管理器 认证信息 用户名 密码
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 基于内存的认证方式
        /*auth
        .inMemoryAuthentication()
        .withUser("admin")
         // 密码设置需要进行加密后才可以，不支持明文的方式 There is no PasswordEncoder
        .password(passwordEncoder().encode("admin"))
        .authorities("admin");*/ // 权限集合不能为空
        // 基于数据库的认证方式
        auth.userDetailsService(userDetailsServiceImpl);
        // super.configure(auth);
    }

    /**
     * 主要是针对系统资源权限进行拦截  控制系统资源权限
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /*http.httpBasic()
                .and()
                .authorizeRequests()// 认证请求
                .anyRequest().authenticated(); // http basic 方式进行认证 所有的请求都需要进行认证*/

        // http.formLogin().and().authorizeRequests().anyRequest().authenticated(); 简单的表单登录配置 默认的登录页面
        AuthProperties auth = customSpringSecurityProperties.getAuth();

        http
                .addFilterBefore(mobileValidateFilter,UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                // 表单登录方式 设置登录页面 -> 指向 controller 包下的接口 并且需要放行 该接口 permitAll为放行配置 并且该配置需要放在所有请求都需要拦截（anyRequest()）之前
                .loginPage(auth.getLoginPage()).permitAll()
                .usernameParameter(auth.getUsername()) // 这里可以修改登录的默认的请求参数
                .passwordParameter(auth.getPassword())
                .loginProcessingUrl(auth.getLoginProcessUrl()) // 登录接口
                .successHandler(customAuthSuccessHandler) // 登陆成功的handler
                .failureHandler(customAuthFailureHandler) // 登录失败的handler
        .and()
        .authorizeRequests()
                /*.antMatchers(auth.getWhiteUrl()).permitAll()
                .anyRequest() // 所有的请求都需要身份认正
                .authenticated()*/
                .and()
                .rememberMe()
                .tokenValiditySeconds(30) // 设置remember-me 时长
                .tokenRepository(jdbcTokenRepository())
                .and()
                .sessionManagement()
                .invalidSessionStrategy(invalidSessionStrategy)
                .maximumSessions(1)
                .expiredSessionStrategy(customSessionExpiredStrategy) // 每一个用户最大的session数 会自动将其他用户踢下线
                // .maxSessionsPreventsLogin(true) 一般情况下不会开启 开启后记住我功能会失效 退出功能也需要自己实现相关的逻辑
                .sessionRegistry(sessionRegistry())
                .and().and().
                logout().addLogoutHandler(customLogoutHandler); // 如果session达到最大用户数就不允许再登录

        http.csrf().disable(); // 关闭跨站请求伪造
        http.apply(mobileAuthConfig);
        authConfigureManager.configure(http.authorizeRequests());

    }

    @Autowired
    private CustomLogoutHandler customLogoutHandler;

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }


    /**
     * 这里是配置所有的静态页面 放行的配置
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(customSpringSecurityProperties.getAuth().getStaticPath());
    }
}
